Facebook Login Button: Workaround for bug of Chrome on iOS - facebook-login

Currently, I am trying to solve a bug logged in Facebook Developer:
https://developers.facebook.com/bugs/325086340912814/
I am using ASP.NET MVC 3 with Facebook C# SDK and Facebook Javascript SDK.
Here is javascript code:
window.fbAsyncInit = function () {
// init the FB JS SDK
FB.init({
appId: '298365513556623', // App ID from the App Dashboard
channelUrl: 'http://www.christianinfoportal.com/channel.html', // Channel File for x-domain communication
status: true, // check the login status upon init?
cookie: true, // set sessions cookies to allow your server to access the session?
xfbml: true // parse XFBML tags on this page?
});
// Additional initialization code such as adding Event Listeners goes here
FB.Event.subscribe('auth.authResponseChange', function (response) {
if ((response.status == 'connected')) {
if (fbIgnoreFirstEventFire)
fbIgnoreFirstEventFire = false;
else {
// the user is logged in and has authenticated your
// app, and response.authResponse supplies
// the user's ID, a valid access token, a signed
// request, and the time the access token
// and signed request each expire
var uid = response.authResponse.userID;
var accessToken = response.authResponse.accessToken;
// TODO: Handle the access token
// Do a post to the server to finish the logon
// This is a form post since we don't want to use AJAX
var form = document.createElement("form");
form.setAttribute("method", 'post');
form.setAttribute("action", '/FBLogin');
var field = document.createElement("input");
field.setAttribute("type", "hidden");
field.setAttribute("name", 'accessToken');
field.setAttribute("value", accessToken);
form.appendChild(field);
var field = document.createElement("input");
field.setAttribute("type", "hidden");
field.setAttribute("name", 'returnUrl');
field.setAttribute("value", window.location.href);
form.appendChild(field);
document.body.appendChild(form);
form.submit();
}
} else if (response.status == 'not_authorized') {
// the user is logged in to Facebook,
// but has not authenticated your app
} else {
// the user isn't logged in to Facebook.
}
});
};
// Load the SDK's source Asynchronously
(function (d, debug) {
var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) { return; }
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all" + (debug ? "/debug" : "") + ".js";
ref.parentNode.insertBefore(js, ref);
}(document, /*debug*/ false));
The View code is pretty simple:
<div id="fbLoginButton" class="fb-login-button" data-show-faces="false" data-width="50" data-max-rows="1" data-scope="email"></div>
I basically forward the access token to a server side code page:
public ActionResult FBLogin(String returnUrl)
{
var accessToken = Request["accessToken"];
//FB fires even without the user clicking the Log In button
Session["FBLoginFirstClick"] = true;
//Session["AccessToken"] = accessToken;
//this.lblAccessToken.Text = accessToken;
//Response.Redirect("/MyUrlHere");
var client = new Facebook.FacebookClient(accessToken);
dynamic me = client.Get("me");
String email, fullName;
fullName = me.first_name + " " + me.last_name;
email = me.email;
Models.User.ThirdPartyLogin(email, fullName, Session);
if (!string.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}

Solved by using Facebook authentication redirect.
https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/

Related

Set multiple cookies in google app script with UrlFetchApp

I am using the following script to access a web page that requires user to be logged-in. The script works up to the point where I retrieve the login cookies I need help with using the cookies in the last part of the following code.
function login(strUsername, strPassword) {
//request Page
var loginPage = UrlFetchApp.fetch("https://login.url.org/");
//strip out "authState" value from response
var sessionDetails = loginPage.getContentText()
var searchString = sessionDetails.indexOf('"authState":"');
var newStringRight = sessionDetails.substring(searchString+13, searchString+6000);
var authState = newStringRight.substring(0, newStringRight.indexOf('"'));
//Logger.log(authState);
//set payload
var payload =
{"authState":authState,
"username":strUsername,
"password":strPassword
};
var options =
{
"method" : "post",
"payload" : JSON.stringify(payload),
"followRedirects" : false,
muteHttpExceptions: true,
"contentType": "application/json"
};
//Logger.log(options);
var loginResponse = UrlFetchApp.fetch("https://login.url.com/api/authenticate/credentials", options);
var loginHtml = loginResponse.getContentText();
Logger.log(loginHtml);
// Log has the following response
//{"output":{"method":"SUCCESS","cookies":{"ObSSOCookie":"xxx","ChurchSSO":"yyy"}}}
if (loginHtml.indexOf("SUCCESS")>0){
Browser.msgBox('Pass', 'Successfully loged in.', Browser.Buttons.OK);
//strip out cookies
searchStringStart = loginHtml.indexOf('{"ObSSOCookie":"');
searchStringEnd = loginHtml.indexOf('"}}');
var Cookie = loginHtml.substring(searchStringStart, searchStringEnd+2);
Logger.log(Cookie);
/*
Logger.log(cookie) returns
{"ObSSOCookie":"xxxx","ChurchSSO":"yyyy"}
*/
////////////////////////////Works up to here/////////////////////////////////////////
var options =
{
"headers" : {"cookies" : Cookie},
muteHttpExceptions: true,
"contentType": "application/json"
};
var adminpage= UrlFetchApp.fetch("https://url.com/admin",options );
Logger.log(adminpage);
/*
<h1>Access Denied</h1>
<p>You are not authorized to access this page.</p>
<p>Retry
*/
}else{
Browser.msgBox('Fail', 'Sign in failed. Please try again.', Browser.Buttons.OK);
}
}
I think it may have to do with the fact that I am trying to use multiple cookies {"ObSSOCookie":"xxxx","ChurchSSO":"yyyy"}not sure how to approperatly pass them into the new page call. Any help would be great
Cookies should be sent inside a single Cookie header separated by a ;(semicolon and a space):
Cookie: name1=value1; name2=value2;
Snippet:
var options =
{
"headers" : {"Cookie" : "ObSSOCookie=xxxx; ChurchSSO=yyyy"}},
};
Reference:
HTTP Cookies

How to handle expired access token in asp.net core using refresh token with OpenId Connect

I have configured an ASOS OpenIdConnect Server using and an asp.net core mvc app that uses the "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.0 and "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0". I have tested the "Authorization Code" workflow and everything works.
The client web app processes the authentication as expected and creates a cookie storing the id_token, access_token, and refresh_token.
How do I force Microsoft.AspNetCore.Authentication.OpenIdConnect to request a new access_token when it expires?
The asp.net core mvc app ignores the expired access_token.
I would like to have openidconnect see the expired access_token then make a call using the refresh token to get a new access_token. It should also update the cookie values. If the refresh token request fails I would expect openidconnect to "sign out" the cookie (remove it or something).
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
AuthenticationScheme = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = "myClient",
ClientSecret = "secret_secret_secret",
PostLogoutRedirectUri = "http://localhost:27933/",
RequireHttpsMetadata = false,
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true,
ResponseType = OpenIdConnectResponseType.Code,
AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet,
Authority = http://localhost:27933,
MetadataAddress = "http://localhost:27933/connect/config",
Scope = { "email", "roles", "offline_access" },
});
It seems there is no programming in the openidconnect authentication for asp.net core to manage the access_token on the server after received.
I found that I can intercept the cookie validation event and check if the access token has expired. If so, make a manual HTTP call to the token endpoint with the grant_type=refresh_token.
By calling context.ShouldRenew = true; this will cause the cookie to be updated and sent back to the client in the response.
I have provided the basis of what I have done and will work to update this answer once all work as been resolved.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
AuthenticationScheme = "Cookies",
ExpireTimeSpan = new TimeSpan(0, 0, 20),
SlidingExpiration = false,
CookieName = "WebAuth",
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = context =>
{
if (context.Properties.Items.ContainsKey(".Token.expires_at"))
{
var expire = DateTime.Parse(context.Properties.Items[".Token.expires_at"]);
if (expire > DateTime.Now) //TODO:change to check expires in next 5 mintues.
{
logger.Warn($"Access token has expired, user: {context.HttpContext.User.Identity.Name}");
//TODO: send refresh token to ASOS. Update tokens in context.Properties.Items
//context.Properties.Items["Token.access_token"] = newToken;
context.ShouldRenew = true;
}
}
return Task.FromResult(0);
}
}
});
You must enable the generation of refresh_token by setting in startup.cs:
Setting values to AuthorizationEndpointPath = "/connect/authorize"; // needed for refreshtoken
Setting values to TokenEndpointPath = "/connect/token"; // standard token endpoint name
In your token provider, before validating the token request at the end of the HandleTokenrequest method, make sure you have set the offline scope:
// Call SetScopes with the list of scopes you want to grant
// (specify offline_access to issue a refresh token).
ticket.SetScopes(
OpenIdConnectConstants.Scopes.Profile,
OpenIdConnectConstants.Scopes.OfflineAccess);
If that is setup properly, you should receive a refresh_token back when you login with a password grant_type.
Then from your client you must issue the following request (I'm using Aurelia):
refreshToken() {
let baseUrl = yourbaseUrl;
let data = "client_id=" + this.appState.clientId
+ "&grant_type=refresh_token"
+ "&refresh_token=myRefreshToken";
return this.http.fetch(baseUrl + 'connect/token', {
method: 'post',
body : data,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
}
});
}
and that's it, make sure that your auth provider in HandleRequestToken is not trying to manipulate the request that is of type refresh_token:
public override async Task HandleTokenRequest(HandleTokenRequestContext context)
{
if (context.Request.IsPasswordGrantType())
{
// Password type request processing only
// code that shall not touch any refresh_token request
}
else if(!context.Request.IsRefreshTokenGrantType())
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "Invalid grant type.");
return;
}
return;
}
The refresh_token shall just be able to pass through this method and is handled by another piece of middleware that handles refresh_token.
If you want more in depth knowledge about what the auth server is doing, you can have a look at the code of the OpenIdConnectServerHandler:
https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/blob/master/src/AspNet.Security.OpenIdConnect.Server/OpenIdConnectServerHandler.Exchange.cs
On the client side you must also be able to handle the auto refresh of the token, here is an example of an http interceptor for Angular 1.X, where one handles 401 reponses, refresh the token, then retry the request:
'use strict';
app.factory('authInterceptorService',
['$q', '$injector', '$location', 'localStorageService',
function ($q, $injector, $location, localStorageService) {
var authInterceptorServiceFactory = {};
var $http;
var _request = function (config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
};
var _responseError = function (rejection) {
var deferred = $q.defer();
if (rejection.status === 401) {
var authService = $injector.get('authService');
console.log("calling authService.refreshToken()");
authService.refreshToken().then(function (response) {
console.log("token refreshed, retrying to connect");
_retryHttpRequest(rejection.config, deferred);
}, function () {
console.log("that didn't work, logging out.");
authService.logOut();
$location.path('/login');
deferred.reject(rejection);
});
} else {
deferred.reject(rejection);
}
return deferred.promise;
};
var _retryHttpRequest = function (config, deferred) {
console.log('autorefresh');
$http = $http || $injector.get('$http');
$http(config).then(function (response) {
deferred.resolve(response);
},
function (response) {
deferred.reject(response);
});
}
authInterceptorServiceFactory.request = _request;
authInterceptorServiceFactory.responseError = _responseError;
authInterceptorServiceFactory.retryHttpRequest = _retryHttpRequest;
return authInterceptorServiceFactory;
}]);
And here is an example I just did for Aurelia, this time I wrapped my http client into an http handler that checks if the token is expired or not. If it is expired it will first refresh the token, then perform the request. It uses a promise to keep the interface with the client-side data services consistent. This handler exposes the same interface as the aurelia-fetch client.
import {inject} from 'aurelia-framework';
import {HttpClient} from 'aurelia-fetch-client';
import {AuthService} from './authService';
#inject(HttpClient, AuthService)
export class HttpHandler {
constructor(httpClient, authService) {
this.http = httpClient;
this.authService = authService;
}
fetch(url, options){
let _this = this;
if(this.authService.tokenExpired()){
console.log("token expired");
return new Promise(
function(resolve, reject) {
console.log("refreshing");
_this.authService.refreshToken()
.then(
function (response) {
console.log("token refreshed");
_this.http.fetch(url, options).then(
function (success) {
console.log("call success", url);
resolve(success);
},
function (error) {
console.log("call failed", url);
reject(error);
});
}, function (error) {
console.log("token refresh failed");
reject(error);
});
}
);
}
else {
// token is not expired, we return the promise from the fetch client
return this.http.fetch(url, options);
}
}
}
For jquery you can look a jquery oAuth:
https://github.com/esbenp/jquery-oauth
Hope this helps.
Following on from #longday's answer, I have had success in using this code to force a client refresh without having to manually query an open id endpoint:
OnValidatePrincipal = context =>
{
if (context.Properties.Items.ContainsKey(".Token.expires_at"))
{
var expire = DateTime.Parse(context.Properties.Items[".Token.expires_at"]);
if (expire > DateTime.Now) //TODO:change to check expires in next 5 mintues.
{
context.ShouldRenew = true;
context.RejectPrincipal();
}
}
return Task.FromResult(0);
}

Cannot logout from facebook in a windows8 phone app using phonegap. how can I solve this?

Hello guys I am implementing logout from facebook functionality in my windows 8 phone application. By using the given below code I am able to logout from the facebook but when I again click on the facebook login button, then it automatically logged in without asking for the email and password.
var redir_url1 = "http://www.facebook.com/connect/logout_success.html";
//redir_url1 is used to redirect it
alert("inside prototype logout");
//store the value of accesstoken locally in finalAccessTokens
var finalAccessToken1 = window.localStorage.getItem("finalAccessTokens");
alert("finalAccessToken1" + finalAccessToken1);
var authorize_url = "https://www.facebook.com/logout.php?confirm=1";
//alert("authorize_url" + authorize_url);
authorize_url += "next=" + redir_url1;
authorize_url += "&access_token=" + finalAccessToken1;
alert("logout url: " + authorize_url);
resetSession();
showWebPage1(authorize_url);
//call a function to open the webpage
}
function showWebPage1(loc) {
alert("logout loc" + loc);
// var locs=this.loc;
cordova.exec(success1, error1, "InAppBrowser", "ShowInAppBrowser", loc);
}
function success1(e) {
alert("logout success");
//var accessToken = window.localStorage.getItem("finalAccessTokens");
// var url = 'https://graph.facebook.com/me?access_token=' + accessToken;
//localStorage.removeItem(cookies);
//localStorage.removeItem(finalAccessTokens);
// closeAndClearTokenInformation;
//ClearInternetCacheAsync();
alert("After removing access token" + `enter code here`window.localStorage.getItem("finalAccessTokens"));
//finalAccessTokens is used to locally store the value of access token
window.localStorage.clear();
alert("success" + JSON.stringify(e));
var successLogout = JSON.stringify(e);
if ((successLogout.indexOf('https://www.facebook.com/home.php') != -1) &&
(successLogout.indexOf('loadstop') != -1)) {
alert("sss in close");
cordova.exec(null, null, "InAppBrowser", "close", []);
alert("after the handle is closed.....");
this.resetSession();
//to reset the session
}
}
function error1() {
alert("err");
}
FBConnect.prototype.resetSession = function () {
alert("session reset");
this.status = "unknown";
this.session = {};
alert("clear access token/////");
this.session.access_token = null;
alert(this.session.access_token);
this.session.expires = new Date().valueOf() - 1000;
this.session.secret = null;
this.session.session_key = null;
this.session.sig = null;
this.session.uid = null;
alert(this.session.uid);
}
You have to remove WebBrowser cookies after you logout. I am not sure how you can do that using PhoneGap, but in a C#/XAML app you can remove them like this:
await new WebBrowser().ClearCookiesAsync();

Unable to access the user session in socket.io by parsing the cookie

I am tring to access the user session into my socket connection in order to accept/refuse it.
The problem is, i do get a sessionId string but the session i get with SessionStore.get() is undefined.
I guess the problem could be that i never "signed" the sessionId after getting it (data.cookie['connect.sid']), but i didn't see any way to do it with the cookie module.
var MongoStore = require('connect-mongo')(express);
var SessionStore = new MongoStore({ db: "test" });
var parseCookie = require('cookie').parse;
app.use(express.session({
secret: 'test',
store: SessionStore
}));
io.set('authorization', function(data, accept) {
if (data.headers.cookie) {
data.cookie = parseCookie(data.headers.cookie);
data.sessionID = data.cookie['connect.sid'];
console.log('sessionId: '+data.sessionID);
SessionStore.get(data.sessionID, function(err, session) {
if (err || !session) {
console.log('err: '+err);
console.log('Session: '+session);
accept('Error', false);
} else {
data.session = session;
accept(null, true);
}
});
} else {
return accept('No cookie transmitted.', false);
}
});
Maybe someone see what i am missing here?
The connect.sid cookie is formatted into 2 parts with a prefix. The actual session ID part can be extracted with this fragment:
rawSid.split(".")[0].slice(2);

Upload an image to Drupal 7 / Services 3 from Titanium?

This must be close but I can't figure out what's causing the error.
In my Titanium app, I have a Webview with a canvas element and this code:
function getImageData() {
return canvas.toDataURL('image/png');
}
I am moving that data to the Titanium app like this:
var imageBase64data = webview.evalJS('getImageData()')
The data looks good starting with "data:image/png;base64,"...
Then in Titanium, I have a logged-in drupal session and call this function:
function uploadImage(imageBase64data, callback) {
var url = REST_PATH + "file.json";
var file = {
filename: utils.createRandomString() + ".png",
file: imageBase64data
// uid: Titanium.App.Properties.getString("userUid"),
// filesize: ""+Titanium.Utils.base64decode(imageBase64data).length,
};
var xhr = Titanium.Network.createHTTPClient({timeout: 30000});
xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
var authString = Titanium.App.Properties.getString("userSessionName")+'='+Titanium.App.Properties.getString("userSessionId");
xhr.setRequestHeader("Cookie", authString);
xhr.onload = function() {
if(xhr.status == 200) {
var response = xhr.responseText;
callback(response);
}
};
xhr.onerror = function(e) {
alert("There was an error: " + e.error);
Ti.API.info(JSON.stringify(this));
};
xhr.open("POST", url);
xhr.send(file);
}
xhr.onerror is being called with e.error = "undefined"
The trace looks like this:
{
"responseData":{},
"readyState":4,
"connected":true,"UNSENT":0,"DONE":4,"HEADERS_RECEIVED":2,"OPENED":1,"LOADING":3,
"responseText":null,"status":406
}
I think authentication is working because I was previously getting a "need authentication" error until I added the Cookie header.
That was with the installation provided by Drupanium. I just did a fresh Drupal and fresh Services 3 install and my file is uploading nicely.