I've been tasked to build a web interface for an Android app based on firebase.
I've got a handful of endpoints, that interact with the database (Cloud functions). To access those endpoints I need to authenticate an user with email and password[1], retrieve an accessToken[2] und authorize every request to the endpoints with an Authorization: Bearer {accessToken} header.
I use php and struggle to wrap my mind around how to manage authenticated user in my app.
TL;DR please see my final solution in php only. https://stackoverflow.com/a/52119600/814031
I transfer the accessToken via ajax in a php session, to sign the cURL requests to the endpoints.
Apparently there is no other way around than use the firebase JS auth (not as far as I understand[4]).
My question is: Is it enough to save the accessToken in a php session and compare it with every page load via an ajax POST request (see code below)?
What would be a more robust strategy to handle that in php?
Edit: A user pointed out that using classic php sessions with JWT tokens don't make much sense and I read up about that topic.
So regarding Firebase - is this something to consider?
https://firebase.google.com/docs/auth/admin/manage-cookies
Firebase Auth provides server-side session cookie management for traditional websites that rely on session cookies. This solution has several advantages over client-side short-lived ID tokens, which may require a redirect mechanism each time to update the session cookie on expiration:
Here is what I got:
1. Login Page
As described in the Firebase examples[3]
function initApp() {
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
// User is signed in.
// obtain token, getIdToken(false) = no forced refresh
firebase.auth().currentUser.getIdToken(false).then(function (idToken) {
// Send token to your backend via HTTPS
$.ajax({
type: 'POST',
url: '/auth/check',
data: {'token': idToken},
complete: function(data){
// data = {'target' => '/redirect/to/route'}
if(getProperty(data, 'responseJSON.target', false)){
window.location.replace(getProperty(data, 'responseJSON.target'));
}
}
});
// ...
}).catch(function (error) {
console.log(error);
});
} else {
// User Signed out
$.ajax({
type: 'POST',
url: '/auth/logout',
complete: function(data){
// data = {'target' => '/redirect/to/route'}
if(getProperty(data, 'responseJSON.target', false)){
// don't redirect to itself
// logout => /
if(window.location.pathname != getProperty(data, 'responseJSON.target', false)){
window.location.replace(getProperty(data, 'responseJSON.target'));
}
}
}
});
// User is signed out.
}
});
}
window.onload = function () {
initApp();
};
2. a php controller to handle the auth requests
public function auth($action)
{
switch($action) {
// auth/logout
case 'logout':
unset($_SESSION);
// some http status header and mime type header
echo json_encode(['target' => '/']); // / => index page
break;
case 'check':
// login.
if(! empty($_POST['token']) && empty($_SESSION['token'])){
// What if I send some bogus data here? The call to the Endpoint later would fail anyway
// But should it get so far?
$_SESSION['token'] = $_POST['token'];
// send a redirect target back to the JS
echo json_encode(['target' => '/dashboard']);
break;
}
if($_POST['token'] == $_SESSION['token']){
// do nothing;
break;
}
break;
}
}
3. the Main controller
// pseudo code
class App
{
public function __construct()
{
if($_SESSION['token']){
$client = new \GuzzleHttp\Client();
// $user now holds all custom access rights within the app.
$this->user = $client->request(
'GET',
'https://us-centralx-xyz.cloudfunctions.net/user_endpoint',
['headers' =>
[
'Authorization' => "Bearer {$_SESSION['token']}"
]
]
)->getBody()->getContents();
}else{
$this->user = null;
}
}
public function dashboard(){
if($this->user){
var_dump($this->user);
}else{
unset($_SESSION);
// redirect to '/'
}
}
}
Note: I'm aware of this sdk https://github.com/kreait/firebase-php and I read a lot in the issues there and in posts here on SO, but I got confused, since there is talk about full admin rights etc. and I really only interact with the endpoints that build upon firebase (plus firebase auth and firestore). And I'm still on php 5.6 :-/
Thanks for your time!
[1]: https://firebase.google.com/docs/auth/web/password-auth
[2]: https://firebase.google.com/docs/reference/js/firebase.User#getIdToken
[3]: https://github.com/firebase/quickstart-js/blob/master/auth/email-password.html
[4]: https://github.com/kreait/firebase-php/issues/159#issuecomment-360225655
I have to admit, the complexity of the firebase docs and examples and different services, got me so confused, that I thought, authentication for the web is only possible via JavaScript. That was wrong. At least for my case, where I just login with email and password to retrieve a Json Web Token (JWT), to sign all calls to the Firebase cloud functions. Instead of juggling with weird Ajax requests or set the token cookie via JavaScript, I just needed to call the Firebase Auth REST API
Here is a minimal case using the Fatfreeframework:
Login form
<form action="/auth" method="post">
<input name="email">
<input name="password">
<input type="submit">
</form>
Route
$f3->route('POST /auth', 'App->auth');
Controller
class App
{
function auth()
{
$email = $this->f3->get('POST.email');
$password = $this->f3->get('POST.password');
$apiKey = 'API_KEY'; // see https://firebase.google.com/docs/web/setup
$auth = new Auth($apiKey);
$result = $auth->login($email,$password);
if($result['success']){
$this->f3->set('COOKIE.token',$result['idToken']);
$this->f3->reroute('/dashboard');
}else{
$this->f3->clear('COOKIE.token');
$this->f3->reroute('/');
}
}
}
Class
<?php
use GuzzleHttp\Client;
class Auth
{
protected $apiKey;
public function __construct($apiKey){
$this->apiKey = $apiKey;
}
public function login($email,$password)
{
$client = new Client();
// Create a POST request using google api
$key = $this->apiKey;
$responsee = $client->request(
'POST',
'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=' . $key,
[
'headers' => [
'content-type' => 'application/json',
'Accept' => 'application/json'
],
'body' => json_encode([
'email' => $email,
'password' => $password,
'returnSecureToken' => true
]),
'exceptions' => false
]
);
$body = $responsee->getBody();
$js = json_decode($body);
if (isset($js->error)) {
return [
'success' => false,
'message' => $js->error->message
];
} else {
return [
'success' => true,
'localId' => $js->localId,
'idToken' => $js->idToken,
'email' => $js->email,
'refreshToken' => $js->refreshToken,
'expiresIn' => $js->expiresIn,
];
}
}
}
Credits
Sounds like #Chad K is getting you on the right track (cookies and ajax - breakfast of champions... :), though I thought to share my code from my working system (with some 'privacy' things, of course!)
Look for /**** type comments for things you need to set up yourself (you may want to do some other firebase things differently as well - see the docs...)
LOGIN.php page (I found it simpler overall to keep this separate - see notes to learn why....)
<script>
/**** I picked this up somewhere off SO - kudos to them - I use it a lot!.... :) */
function setCookie(name, value, days = 7, path = '/') {
var expires = new Date(Date.now() + days * 864e5).toUTCString();
document.cookie = name + '=' + encodeURIComponent(value) + '; expires=' + expires + '; path=' + path;
}
function getCookie(c_name) {
if (document.cookie.length > 0) {
c_start = document.cookie.indexOf(c_name + "=");
if (c_start !== -1) {
c_start = c_start + c_name.length + 1;
c_end = document.cookie.indexOf(";", c_start);
if (c_end === -1) {
c_end = document.cookie.length;
}
return unescape(document.cookie.substring(c_start, c_end));
}
}
return "";
}
</script>
<script>
var config = {
apiKey: "your_key",
authDomain: "myapp.firebaseapp.com",
databaseURL: "https://myapp.firebaseio.com",
projectId: "myapp",
storageBucket: "myapp.appspot.com",
messagingSenderId: "the_number"
};
firebase.initializeApp(config);
</script>
<script src="https://cdn.firebase.com/libs/firebaseui/2.7.0/firebaseui.js"></script>
<link type="text/css" rel="stylesheet" href="https://cdn.firebase.com/libs/firebaseui/2.7.0/firebaseui.css"/>
<script type="text/javascript">
/**** set this url to the 'logged in' page (mine goes to a dashboard) */
var url = 'https://my.app/index.php#dashboard';
/**** by doing this signOut first, then it is simple to send any 'logout' request in the app to 'login.php' - one page does it.... :) */
firebase.auth().signOut().then(function () {
}).catch(function (error) {
console.log(error);
});
var signInFlow = 'popup';
if (('standalone' in window.navigator)
&& window.navigator.standalone) {
signInFlow = 'redirect';
}
var uiConfig = {
callbacks: {
signInSuccessWithAuthResult: function (authResult, redirectUrl) {
/**** here you can see the logged in user */
var firebaseUser = authResult.user;
var credential = authResult.credential;
var isNewUser = authResult.additionalUserInfo.isNewUser;
var providerId = authResult.additionalUserInfo.providerId;
var operationType = authResult.operationType;
/**** I like to force emailVerified...... */
if (firebaseUser.emailVerified !== true) {
firebase.auth().currentUser.sendEmailVerification().then(function () {
/**** if using this, you can set up your own usermgmt.php page for the user verifications (see firebase docs) */
window.location.replace("https://my.app/usermgmt.php?mode=checkEmail");
}).catch(function (error) {
console.log("an error has occurred in sending verification email " + error)
});
}
else {
var accessToken = firebaseUser.qa;
/**** set the Cookie (yes, I found this best, too) */
setCookie('firebaseRegistrationID', accessToken, 1);
/**** set up the AJAX call to PHP (where you will store this data for later lookup/processing....) - I use "function=....." and "return=....." to have options for all functions and what to select for the return so that ajax.php can be called for 'anything' (you can just call a special page if you like instead of this - if you use this idea, be sure to secure the ajax.php 'function' call to protect from non-authorized use!) */
var elements = {
function: "set_user_data",
user: JSON.stringify(firebaseUser),
return: 'page',
accessToken: accessToken
};
$.ajaxSetup({cache: false});
$.post("data/ajax.php", elements, function (data) {
/**** this calls ajax and gets the 'page' to set (this is from a feature where I store the current page the user is on, then when they log in again here, we go back to the same page - no need for cookies, etc. - only the login cookie is needed (and available for 'prying eyes' to see!) */
url = 'index.php#' + data;
var form = $('<form method="post" action="' + url + '"></form>');
$('body').append(form);
form.submit();
});
}
return false;
},
signInFailure: function (error) {
console.log("error - signInFailure", error);
return handleUIError(error);
},
uiShown: function () {
var loader = document.getElementById('loader');
if (loader) {
loader.style.display = 'none';
}
}
},
credentialHelper: firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM,
queryParameterForWidgetMode: 'mode',
queryParameterForSignInSuccessUrl: 'signInSuccessUrl',
signInFlow: signInFlow,
signInSuccessUrl: url,
signInOptions: [
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
// firebase.auth.FacebookAuthProvider.PROVIDER_ID,
// firebase.auth.TwitterAuthProvider.PROVIDER_ID,
{
provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
requireDisplayName: true,
customParameters: {
prompt: 'select_account'
}
}
/* {
provider: firebase.auth.PhoneAuthProvider.PROVIDER_ID,
// Invisible reCAPTCHA with image challenge and bottom left badge.
recaptchaParameters: {
type: 'image',
size: 'invisible',
badge: 'bottomleft'
}
}
*/
],
tosUrl: 'https://my.app/login.php'
};
var ui = new firebaseui.auth.AuthUI(firebase.auth());
(function () {
ui.start('#firebaseui-auth-container', uiConfig);
})();
</script>
Now, on every page you want the user to see (in my case, it all goes through index.php#something - which makes it easier.... :)
<script src="https://www.gstatic.com/firebasejs/4.12.0/firebase.js"></script>
<script>
// Initialize Firebase - from https://github.com/firebase/firebaseui-web
var firebaseUser;
var config = {
apiKey: "your_key",
authDomain: "yourapp.firebaseapp.com",
databaseURL: "https://yourapp.firebaseio.com",
projectId: "yourapp",
storageBucket: "yourapp.appspot.com",
messagingSenderId: "the_number"
};
firebase.initializeApp(config);
initFBApp = function () {
firebase.auth().onAuthStateChanged(function (firebaseuser) {
if (firebaseuser) {
/**** here, I have another ajax call that sets up some select boxes, etc. (I chose to call it here, you can call it anywhere...) */
haveFBuser();
firebaseUser = firebaseuser;
// User is signed in.
var displayName = firebaseuser.displayName;
var email = firebaseuser.email;
var emailVerified = firebaseuser.emailVerified;
var photoURL = firebaseuser.photoURL;
if (firebaseuser.photoURL.length) {
/**** set the profile picture (presuming you are showing it....) */
$(".profilepic").prop('src', firebaseuser.photoURL);
}
var phoneNumber = firebaseuser.phoneNumber;
var uid = firebaseuser.uid;
var providerData = firebaseuser.providerData;
var string = "";
firebaseuser.getIdToken().then(function (accessToken) {
// document.getElementById('sign-in-status').textContent = 'Signed in';
// document.getElementById('sign-in').textContent = 'Sign out';
/**** set up another ajax call.... - to store things (yes, again.... - though this time it may be due to firebase changing the token, so we need it twice...) */
string = JSON.stringify({
displayName: displayName,
email: email,
emailVerified: emailVerified,
phoneNumber: phoneNumber,
photoURL: photoURL,
uid: uid,
accessToken: accessToken,
providerData: providerData
});
if (accessToken !== '<?php echo $_COOKIE['firebaseRegistrationID']?>') {
console.log("RESETTING COOKIE with new accessToken ");
setCookie('firebaseRegistrationID', accessToken, 1);
var elements = 'function=set_user_data&user=' + string;
$.ajaxSetup({cache: false});
$.post("data/ajax.php", elements, function (data) {
<?php
/**** leave this out for now and see if anything weird happens - should be OK but you might want to use it (refreshes the page when firebase changes things..... I found it not very user friendly as they reset at 'odd' times....)
/*
// var url = 'index.php#<?php echo(!empty($user->userNextPage) ? $user->userNextPage : 'dashboard'); ?>';
// var form = $('<form action="' + url + '" method="post">' + '</form>');
// $('body').append(form);
// console.log('TODO - leave this form.submit(); out for now and see if anything weird happens - should be OK');
// form.submit();
*/
?>
});
}
});
} else {
console.log("firebase user CHANGED");
document.location.href = "../login.php";
}
}, function (error) {
console.log(error);
}
);
};
window.addEventListener('load', function () {
initFBApp();
});
</script>
Hope this helps. It is from my working system, which includes some extra features I've put in there along the way, but mostly it is directly from firebase so you should be able to follow along well enough.
Seems a much simpler route to take than your original one.
You really aren't supposed to use sessions in PHP when using tokens. Tokens should be sent in the header on every request (or a cookie works too).
Tokens work like this:
1. You sign in, the server mints a token with some information encoded
2. You send that token back on every request
Based on the information encoded in the token, the server can get information about the user. Typically a User ID of some sort is encoded in it. The server knows it's a valid token because of the way it's encoded.
Send the token on every request you need to make, then in PHP you can just pass that token to the other API
Related
I have a php file on server which have a function, and I have Node Js API also. I want to pass Node Js value to php script then get back the function output to node js.
I tried this using cookie-parser as sugggested by Christian in here. But it does not work
php script
<?php
$max = $_COOKIE["usrMob"]; // Taken from cookie
$min = 1111;
$number = mt_rand($min, $max); // Find random number
echo $number; // Send back to Node Js
?>
Node.Js
const express = require("express");
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser('Your Secret'));
router.get('/cookie', function (req,res)
{
// Set cookie
res.cookie('userMax', '46556') // options is optional
res.end();
console.log("Cookie is : " + res.cookie);
})
I have a php file on server which have a function, and I have Node Js
API also. I want to pass Node Js value to php script then get back the
function output to node js.
I tried this using cookie-parser as sugggested by Christian in here. But it does not work
Short answer
Sharing COOKIES won't work because of CORS, your nodejs server must be in the allow origin list of the PHP server.
Long answer
COOKIES are very used when storing user settings/tokens/password or some sensitive data on your browser that allows the user browsing experience behave different mostly the user decisions.
Therefore they cannot be sent in requests when different servers communicates between them unless they are allowed to leave to an 'authorized-origin' otherwise that would be a major leak of data through cookies, say hello to CORS (unless you don't own the target server).
Example:
You have a script on a TargetServer(TS), that sets a cookie there when user does some stuff. After the user finishes with your script you want to send data back to YourServer(YS), when the AJAX triggers, cookies won't be sent with the request as you normally see when you develop on localhost.
Following your stack of tools, another problem issues, each request that you'll make to YS will generate a new id/session (i'm looking at you PHPSESSID), and that means, you won't know for example if the user is logged or not, and you know for sure that he already logged earlier (Yes - he is logged, but in another session file ... ).
HOW TO TACKLE THIS PROBLEM:
Find an appropriate mechanism for encrypt/decrypt strings that your script and php will know.
When you're sending a request from TS to YS add a custom
header that YS will expect.eg. REQUEST-CUSTOM-HEADER: encodedVersionOf('hey-give-me-the-session-id') , PHP will see the incoming header, will decodeVersionOf('hey-give-me-the-session-id') and will trigger some special if and send you a response with a different header RESPONSE-CUSTOM-HEADER: encodedVersionOf('here-is-the-session-id'). Your script will now save it in COOKIES so you won't have to request it again. and just append it to your header on future requests.
If PHP recognizes the incoming string as a valid session then php can load that session that you know you had data in it with session_id($incoming_id), make sure to set session_id before session_start
I highly advise using JWT for this kind of things or some encrypted stringify json, so you can have an object like {session_id : 12idn3oind, userInfo: {name: 'test'}}.
Exchanging data through headers is the next best thing when CORS is involved.
I tackled this example once, wasn't pretty to do, but worth it in the end.
You can send/receive data to/from php, only thing is that you should use headers so you won't affect php output.
Since you own both servers you can do something like:
MOST IMPORTANT :
npm install -S express
Make sure you have enabled headers_module/mod_headers on your webserver.
We will use custom headers so you should allow & expose them:
.htaccess
Header add Access-Control-Allow-Headers "node-request, node-response"
Header add Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS"
Header add Access-Control-Expose-Headers "node-request, node-response"
Header add Access-Control-Allow-Origin "*"
PHP
<?php
$max = #$_COOKIE["usrMob"]; // Taken from cookie
$min = 1111;
$number = rand($min, $max); // Find random number
echo $number; // Send back to Node Js
if( isset($_SERVER['HTTP_NODE_REQUEST'])){
$req = json_decode($_SERVER['HTTP_NODE_REQUEST'], true);
$data = array();
// 'givemeanumber' is sent from Node.js server
if( isset($req['givemeanumber']) ){
$data = array(
'number' => $number
);
}
header('NODE-RESPONSE: '. json_encode(array("req" => $req, "res"=> $data)));
}
?>
Node.JS
Don't forget to change these line to point to your php-server:
getFromPHP('localhost', '9999', '/path-to-php-script', {givemeanumber: 1})
index.js
const express = require("express");
const app = express();
const port = 9999;
const { getFromPHP } = require('./middleware.js');
const apachePHPconfig = {
host: 'localhost',
port: 80,
urlpath: 'path-to-php-script'
}
app.get(
'/',
getFromPHP(apachePHPconfig.host, apachePHPconfig.port, apachePHPconfig.urlpath , {givemeanumber: 1}),
function (req, res) {
// here is your php object
console.log('php', req.php);
res.end();
})
app.listen(port, () => {
console.clear();
console.log(`Example app listening on port ${port}!`)
})
middleware.js
/**
* Middleware to get data from PHP
*/
const getFromPHP = (phpHost, phpPort, phpPath, phpObject) => {
if (typeof phpHost === 'undefined') {
throw new Error('phpHost was not defined');
}
if (typeof phpPort === 'undefined') {
throw new Error('phpPort was not defined');
}
if (typeof phpPath === 'undefined') {
phpPath = '/';
}
if (typeof phpObject !== 'object' ) {
phpObject = {};
}
return (req, res, next) => {
if (typeof req.php === 'undefined') {
req.php = {};
}
const options = {
hostname: phpHost, // change this to your php server host
port: phpPort, // change this with your php server port
path: phpPath, // change this with your php server path to script
method: 'POST',
headers: {
// here we send 'NODE-REQUEST', it will be available in php unde $_SERVER global prefixed with HTTP_ string because is a custom client request header.
'NODE-REQUEST': JSON.stringify(phpObject)
}
};
const isJSON = (str ) => {
try {
let j = JSON.parse(str);
return typeof j === 'object' && j !== null;
} catch (e) {
return false;
}
};
const httpModule = require('http');
let reqHttp = httpModule.request(options, (response) => {
if( typeof response.headers['node-response'] === 'undefined' || !isJSON(response.headers['node-response'])){
req.php = {};
}else{
req.php = JSON.parse(response.headers['node-response']);
}
// START - Remove this code when everything runs as expected
let dataStack = [];
response.on('data', (data)=>{
dataStack.push(data.toString());
})
response.on('end', ()=>{
console.log("PHP HEADERS", response.headers)
console.log('PHP OUTPUT', dataStack.join(''));
})
// END
next();
});
reqHttp.on('error', (e) => {
console.error(`problem with request to php server: ${e.message}`);
next();
});
reqHttp.on('end', () => {
next();
});
reqHttp.end();
}
}
exports.getFromPHP = getFromPHP;
I'm struggling with getting additional scope information from the Google PHP API. I'm using it in conjunction with JavaScript to get an access token (not sure if this is the correct way, but it works for me)
I have a Google sign up button on my page that's connected to the following function. Basically, it gets a response token to send to my PHP server trough AJAX.
gapi.load('auth2', function() {
// Retrieve the singleton for the GoogleAuth library and set up the client.
auth2 = gapi.auth2.init({
client_id: 'XXXX',
cookie_policy: 'single_host_origin',
// Requesting additional scopes
scope: 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/plus.login'
});
auth2.attachClickHandler(document.getElementById('google-login-signup'), {},
function(googleUser) {
if ( auth2.isSignedIn.get() ) {
var data = {
'action': 'social_google_login',
'_nonce': $('#google-login-signup').attr('data-nonce'),
'redirect_to': $('#google-login-signup').attr('data-redirect-to'),
'token': googleUser.getAuthResponse().id_token
}
$.ajax({
url: ajax_url,
type: 'POST',
data: data,
success: function(response) {
console.log(response);
if ( response.success === true ) {
window.location.href = response.data.redirect;
}
}
});
}
}, function(error) {
console.log(error);
}
);
});
Then on my server, the token is retrieved and fed through the following function, which checks if the token is valid and returns the info:
public function connect() {
$client = new Google_Client();
$credentials = json_decode('XXXX', true);
$client->setAuthConfig($credentials);
$payload = $client->verifyIdToken($_POST['token']);
if ( !$payload ) {
return new WP_Error('invalid_payload', 'The payload was invalid.');
}
return $payload;
}
This all works fine, except that it doesn't include the information from the additional scopes I requested in the JavaScript function. How can I get this additional scope information such as the birthday and sex?
Just for reference, this is what the $payload variable returns:
at_hash: "XXXX"
aud: "XXXX.apps.googleusercontent.com"
azp: "XXXX.apps.googleusercontent.com"
email: "XXXX#gmail.com"
email_verified: true
exp: 1520189629
family_name: "XXXX"
given_name: "XXXX"
iat: XXXX
iss: "accounts.google.com"
jti: "XXXX"
locale: "en"
name: "XXXX XXXX"
picture: "XXXX"
sub: "XXXX"
I managed to figure it out. The main problem was I was trying to access data through the id_token, but what I needed to do was use an access_token and pass it through other Google APIs.
In case anyone stumbles upon this, here is my new and improved code, which also fixes some issues I had unrelated to this question.
JavaScript
$('#google-login-signup').on('click', function(e) {
e.preventDefault();
gapi.load('auth2', function() {
var scopes = [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/plus.login'
];
// Use gapi.auth2.authorize instead of gapi.auth2.init.
// This is because I only need the data from Google once.
gapi.auth2.authorize({
'client_id': 'XXXX.apps.googleusercontent.com',
'cookie_policy': 'single_host_origin',
'fetch_basic_profile': false,
'ux_mode': 'popup',
'scope': scopes.join(' '),
'prompt': 'select_account'
},
function(googleResponse) {
if ( googleResponse.error ) {
return;
}
var data = {
'action': 'social_google_login',
'_nonce': $('#google-login-signup').attr('data-nonce'),
'redirect_to': $('#google-login-signup').attr('data-redirect-to'),
// Instead of id_token, send the access_token.
// This is needed for accessing the scope info from other APIs.
'access_token': googleResponse.access_token
}
$.ajax({
url: ajax_url,
type: 'POST',
data: data,
success: function(response) {
if ( response.success === true ) {
window.location.href = response.data.redirect;
}
}
});
});
});
});
PHP
public function connect() {
$client = new Google_Client();
$credentials = json_decode('XXXX', true);
$client->setAuthConfig($credentials);
// Set Access Token
$client->setAccessToken($_POST['access_token']);
// Connect to Oauth2 API after providing access_token to client
$oauth2 = new Google_Service_Oauth2($client);
if ( !$oauth2 ) {
return new WP_Error('invalid_access_token', 'The access_token was invalid.');
}
// Contains basic user info
$google_user = $this->get_user($oauth2->userinfo->get());
// To get the plus.login scope we need to setup a Google_Service_Plus
$google_plus_service = new Google_Service_Plus($client);
// Contains Google+ profile info
$profile = $google_plus_service->people->get('me');
}
That's it! it was basically an issue of not knowing that I needed to access a different Google_Service to get the additional scope information.
In Google Developers API Console search for Google People API, Enable it and use these scopes as well:
https://www.googleapis.com/auth/contacts | Manage your contacts
https://www.googleapis.com/auth/contacts.readonly | View your contacts
https://www.googleapis.com/auth/plus.login | Know the list of people in your circles, your age range, and language
https://www.googleapis.com/auth/user.addresses.read | View your street addresses
https://www.googleapis.com/auth/user.birthday.read | View your complete date of birth
https://www.googleapis.com/auth/user.emails.read | View your email addresses
https://www.googleapis.com/auth/user.phonenumbers.read | View your phone numbers
https://www.googleapis.com/auth/userinfo.email | View your email address
https://www.googleapis.com/auth/userinfo.profile | View your basic profile info
Lists of all available scopes documented in here
I'm using Yii2 and I have setup a login process which works fine (cookies as well) via the standard login page which is not AJAX.
I have built a dropdown login field which works fine at logging them in, however it doesn't seem to set the cookie as the user doesn't stay logged in and there is no bookie created.
I figured that this was because of AJAX and the cookie wasn't being created on the users system, but upon further reading it seems it should work.
I have verified that the cookie value is being set correctly, the only issue is the cookie doesn't seem to being created.
My login code:
JS:
function doLogin() {
// Set file to prepare our data
var loadUrl = "../../user/login/";
// Set parameters
var dataObject = $('#login_form').serialize();
// Set status element holder
var status_el = $('#login_status');
// Make sure status element is hidden
status_el.hide();
// Run request
getAjaxData(loadUrl, dataObject, 'POST', 'json')
.done(function(response) {
if (response.result == 'success') {
//.......
} else {
//.......
}
})
.fail(function() {
//.......
});
// End
}
function getAjaxData(loadUrl, dataObject, action, type) {
if ($('meta[name="csrf-token"]').length) {
// Add our CSRF token to our data object
dataObject._csrf = $('meta[name="csrf-token"]').attr('content');
}
return jQuery.ajax({
type: action,
url: loadUrl,
data: dataObject,
dataType: type
});
}
Controller:
public function actionLogin() {
// Check if they already logged in
if (!Yii::$app->user->isGuest and !Yii::$app->request->isAjax) {
return $this->redirect('/');
}
if (Yii::$app->request->isAjax) {
// Set our data holder
$response = ['output' => '', 'result' => 'error'];
}
// Let's send the POST data to the model and see if their login was valid
if (Yii::$app->request->isAjax and $this->user->validate() and $this->user->login()) {
$response['result'] = 'success';
} elseif (!Yii::$app->request->isAjax and Yii::$app->request->isPost and $this->user->validate() and $this->user->login()) {
//.......
} else {
//.......
}
if (Yii::$app->request->isAjax) {
echo Json::encode($response);
}
}
Model:
public function login() {
// Set cookie expire time
$cookie_expire = 3600 * 24 * Yii::$app->params['settings']['cookie_expire'];
return Yii::$app->user->login($this->getUser(), ($this->remember_me ? $cookie_expire : 0));
}
As I suspected (see my earlier comment) response might not be correctly generated in case of simply echoing the data. Or maybe Content-Type header matters. If someone can confirm this it will be great.
Anyway, I'm glad it works now (data needs to be returned).
And you can use Response handy format as well.
public function actionLogin() {
// ...
if (Yii::$app->request->isAjax) {
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
return $response;
}
}
I am working an an Ionic app where I implement native Facebook login (followed this tutorial -> https://ionicthemes.com/tutorials/about/native-facebook-login-with-ionic-framework). As you can see the Facebook data now gets stored in local storage. I need to save this data in my MySql database.
I got this to work without any issues. Now I want to store the Facebook user data to my MySql database.
Basically I am not sure where to place my http request to pass the data along to my database or how to even do it code wise.
I should mention that I have a backend already setup (which is coded with bootstrap, html, css, js php and mysql).
So the url for my users would be this: http://www.xxxxx.com/user.php
Part of my controller code:
app.controller('LoginCtrl', function($scope, $state, $q, UserService, $ionicLoading) {
// This is the success callback from the login method
var fbLoginSuccess = function(response) {
if (!response.authResponse){
fbLoginError("Cannot find the authResponse");
return;
}
var authResponse = response.authResponse;
getFacebookProfileInfo(authResponse)
.then(function(profileInfo) {
// For the purpose of this example I will store user data on local storage
UserService.setUser({
authResponse: authResponse,
userID: profileInfo.id,
name: profileInfo.name,
email: profileInfo.email,
picture : "http://graph.facebook.com/" + authResponse.userID + "/picture?type=large"
});
$ionicLoading.hide();
$state.go('app.dashboard');
}, function(fail){
// Fail get profile info
console.log('profile info fail', fail);
});
};
// This is the fail callback from the login method
var fbLoginError = function(error){
console.log('fbLoginError', error);
$ionicLoading.hide();
};
// This method is to get the user profile info from the facebook api
var getFacebookProfileInfo = function (authResponse) {
var info = $q.defer();
facebookConnectPlugin.api('/me?fields=email,name&access_token=' + authResponse.accessToken, null,
function (response) {
console.log('logging facebook response',response);
info.resolve(response);
},
function (response) {
console.log(response);
info.reject(response);
}
);
return info.promise;
};
//This method is executed when the user press the "Login with facebook" button
$scope.facebookSignIn = function() {
facebookConnectPlugin.getLoginStatus(function(success){
if(success.status === 'connected'){
// 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
console.log('getLoginStatus', success.status);
// Check if we have our user saved
var user = UserService.getUser('facebook');
if(!user.userID){
getFacebookProfileInfo(success.authResponse)
.then(function(profileInfo) {
// For the purpose of this example I will store user data on local storage
UserService.setUser({
authResponse: success.authResponse,
userID: profileInfo.id,
name: profileInfo.name,
email: profileInfo.email,
picture : "http://graph.facebook.com/" + success.authResponse.userID + "/picture?type=large"
});
$state.go('app.dashboard');
}, function(fail){
// Fail get profile info
console.log('profile info fail', fail);
});
}else{
$state.go('app.dashboard');
}
} else {
// If (success.status === 'not_authorized') the user is logged in to Facebook,
// but has not authenticated your app
// Else the person is not logged into Facebook,
// so we're not sure if they are logged into this app or not.
console.log('getLoginStatus', success.status);
$ionicLoading.show({
template: 'Logging in...'
});
// Ask the permissions you need. You can learn more about
// FB permissions here: https://developers.facebook.com/docs/facebook-login/permissions/v2.4
facebookConnectPlugin.login(['email', 'public_profile'], fbLoginSuccess, fbLoginError);
}
});
};
})
My service.js code (local storage)
angular.module('Challenger.services', [])
.service('UserService', function() {
// For the purpose of this example I will store user data on ionic local storage but you should save it on a database
var setUser = function(user_data) {
window.localStorage.starter_facebook_user = JSON.stringify(user_data);
};
var getUser = function(){
return JSON.parse(window.localStorage.starter_facebook_user || '{}');
};
return {
getUser: getUser,
setUser: setUser
};
});
My recommendation is to simply use a JSON ajax PUT or POST from JavaScript. For example, assuming a backend host of example.com
Add a CSP to the Ionic HTML such as:
<meta http-equiv="Content-Security-Policy" content="default-src http://example.com; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval'">
Add the domain to the whitelist in the Cordova config.xml:
<access origin="http://example.com" />
Then you can call PHP from JavaScript with ajax in your angular controller (I used jQuery here but you can use any JavaScript ajax library):
var data = {
authResponse: authResponse,
userID: profileInfo.id,
name: profileInfo.name,
email: profileInfo.email,
picture : "http://graph.facebook.com/" + authResponse.userID + "/picture?type=large"
};
$.post( "http://example.com/login.php", data, function(returnData, status) {
console.log('PHP returned HTTP status code', status);
});
Finally, on the PHP side — e.g. login.php — access the post data with $_POST['userId'], $_POST['email'], etc.
I guess that you have all your codes ready, but just not sure where is the best place to locate your codes. There is nice linker where has clear instruction about how to layout your php project structure: http://davidshariff.com/blog/php-project-structure/, hope this can give a kind of help.
I have created a javascript based login for my latest application, and everything works fine. The problem is that i want to store all users in a database, and dont know how to do this the facebook way. I have very good php and sql knowledge, so that is not a problem. I just need some advice on how to securely store the data.
The procedure i want is this:
User login with javascript popup -> check if facebook id exists in mysql table. if not, save with additional info -> user is logged in
<script type="text/javascript">
window.fbAsyncInit = function() {
FB.init({
appId: 'YOUR_APP_ID',
status: true,
cookie: true,
oauth: true
});
FB.Event.subscribe('auth.login', function(response) {
// response returns a JSON object containing data relevant to the logged in user.
userID = response.authResponse.userID;
// using jQuery to perform AJAX POST.
$.post('form_handler.php', {userID: userID}, function() {
// POST callback
});
});
}
</script>
Your form_handler.php file would need to be set up to fetch the userID variable from $_POST. From there you can use SQL to check if the user already exists, etc.
If your concern is that the userID JavaScript variable can be easily tampered with, I suggest using the PHP SDK within the form_handler.php file to grab the current uid. Inside of form_handler.php here (in the most basic form) is what you would need to do:
<?php
require('facebook.php');
$facebook = new Facebook(array(
'appId' => YOUR_APP_ID,
'secret' => YOUR_APP_SECRET
));
// get the current logged in userID
$user = $facebook->getUser();
// SQL queries (check if user exists, insert, etc.)
?>
The above code assumes you've migrated your app to oAuth 2.0.
After a successful login with Facebook JS Login call this function testAPI()
yourJSfile.js
function testAPI() {
console.log('Welcome! Fetching your information.... ');
FB.api('/me', function(response) {
var json = JSON.stringify(response);
setCookie("fbresponse_"+response.id, json, 1);
facebook_response = response;
doLocalPosiive();
return;
for(var propt in response){
console.log(propt + ': ' + response[propt]);
}
});
}
function setCookie(c_name,value,exdays)
{
var exdate=new Date();
exdate.setDate(exdate.getDate() + exdays);
var c_value=encodeURIComponent(value) + ((exdays==null) ? "" : "; expires="+exdate.toUTCString());
document.cookie=c_name + "=" + c_value;
}
The testAPI function will convert the response to JSON string and save it to cookie and on your php page you can retrieve the cookie and parse the signed_request ( and verify the signed request with your valid app_secret which is known to you alone I guess) and decode the JSONed Response, then do whatever you want with it safely in your php/mySQL.
thePHPfile.php
<?php
function getSignedRequest($app_id){
$signed_request = $_COOKIE["fbsr_{$app_id}"];
if($signed_request){
return $signed_request;
} else {
return false;
}
}
function parseSignedRequest($signed_request, $secret){
list($encoded_sig, $payload) = explode('.', $signed_request, 2);
//Decode the data
$sig = base64_url_decode($encoded_sig);
$data = json_decode(base64_url_decode($payload), true);
if(strtoupper($data['algorithm']) !== 'HMAC-SHA256'){
error_log("Unknown Algorithm. Expected HMAC-SHA256");
return null;
}
//Verify the signed_resquest
$expeted_sig = hash_hmac('sha256', $payload, $secret, $raw = true);
if($sig !== $expeted_sig){
error_log("Bad Signed JSON signature!");
return null;
}
return $data;
}
function base64_url_decode($str){
//$str .= str_repeat("=", (4-(strlen($str)%4)));
return base64_decode(strtr($str, '-_', '+/'));
}
// Please edit the next 2 lines
$app_id = "314xxxxxxxxx990";
$app_secret = "56b5eaxxxxxxxxxxxxxxxxxxx37c799";
if($fbsr = getSignedRequest($app_id)){
$response = parseSignedRequest($fbsr, $app_secret);
if($response['user_id']){
$js_response = $_COOKIE["fbresponse_{$response['user_id']}"];
$response_array = (json_decode($js_response, true));
//you can perform your database activities here now
}
}
?>
Please don't forget to edit your APP_ID and APP_SECRET.
I hope you or someone else find this useful.